package top.v5it.japi.plus.core.spring.web.interceptor;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ClassUtil;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import top.v5it.japi.plus.common.util.JackSonUtil;
import top.v5it.japi.plus.common.util.TypeUtil;
import top.v5it.japi.plus.core.ConstantPool;
import top.v5it.japi.plus.core.api.Empty;
import top.v5it.japi.plus.core.api.ErrorResult;
import top.v5it.japi.plus.core.api.Result;
import top.v5it.japi.plus.core.api.ResultStatus;
import top.v5it.japi.plus.core.spring.web.JapiDecryptHttpServletRequestWrapper;

import java.lang.reflect.Method;
import java.util.*;

/**
 * 响应结果包装
 *
 * @author zhanpu
 */
@Slf4j
@RequiredArgsConstructor
@ControllerAdvice
public class JapiResponseBodyWrapperAdvice implements ResponseBodyAdvice<Object> {

    private final JapiResponseWrapper japiResponseWrapper;
    private static final ThreadLocal<Boolean> RESPONSE_RESULT_WRAPPER_HOLDER = new TransmittableThreadLocal<>(Boolean.TRUE);

    /**
     * 拼装方法签名作为key
     *
     * @param beanType
     * @param method
     * @return
     */
    private static String initDescription(Class<?> beanType, Method method) {
        StringJoiner joiner = new StringJoiner(", ", "(", ")");

        for (Class<?> paramType : method.getParameterTypes()) {
            joiner.add(paramType.getSimpleName());
        }

        return beanType.getName() + "#" + method.getName() + joiner.toString();
    }

    /**
     * 是否支持包装结果返回
     *
     * @param returnType
     * @param converterType
     * @return
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        final String description = initDescription(returnType.getDeclaringClass(), Objects.requireNonNull(returnType.getMethod())) + "#japieada";

        Boolean result = Optional.of(JapiEadaCache.getInstance())
                .filter(v -> v.containsKey(description))
                .map(v -> Boolean.TRUE)
                .orElse(Boolean.FALSE);

        RESPONSE_RESULT_WRAPPER_HOLDER.set(result);

        return true;
    }

    /**
     * 包装返回结果
     *
     * @param body
     * @param returnType
     * @param selectedContentType
     * @param selectedConverterType
     * @param request
     * @param response
     * @return
     */
    @Override
    public Object beforeBodyWrite(Object body
            , MethodParameter returnType
            , MediaType selectedContentType
            , Class<? extends HttpMessageConverter<?>> selectedConverterType
            , ServerHttpRequest request, ServerHttpResponse response) {

        Class<?> parameterType = returnType.getParameterType();

        try {
            // 系统本身异常返回
            if (body instanceof LinkedHashMap) {
                return body;
            }

            // map为空
            if (body instanceof Map && MapUtil.isEmpty((Map) body)) {
                return noContent(response);
            }

            //void返回，status=204
            else if (TypeUtil.isVoid(parameterType)) {
                return noContent(response);
            }

            // 基本类型及包装类型
            else if (TypeUtil.isBasicType(parameterType) || TypeUtil.isString(parameterType)) {
                log.error("+++++API返回结果类型只能是void或json对象");
                throw new UnsupportedOperationException();
            }

            // 基本类型，包括包装类型
            else if (ClassUtil.isBasicType(parameterType) && Boolean.FALSE.equals(RESPONSE_RESULT_WRAPPER_HOLDER.get())) {
                return beforeBodyWrite(body, response, true);
            }

            // ErrorResult类型
            else if (ErrorResult.class == parameterType || ErrorResult.class.isAssignableFrom(parameterType)) {
                return body;
            }

            // 数组类型
            else if (TypeUtil.isArray(parameterType) && Objects.isNull(body)) {
                return noContent(response);
            }

            // Optional类型，不支持
            else if (TypeUtil.isOptional(parameterType)) {
                response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
                return ErrorResult.failure(ResultStatus.SYSTEM_ERROR);
            }

            // 集合
            else if (TypeUtil.isCollection(parameterType) && Objects.isNull(body)) {

                // 为null代表没有找到数据，但是请求是成功的
                return noContent(response);
            }

            // 其他对象
            else {
                // 为null代表没有找到数据，但是请求是成功的
                if (Objects.isNull(body)) {
                    return noContent(response);
                }
            }

            return Boolean.FALSE.equals(RESPONSE_RESULT_WRAPPER_HOLDER.get())
                    ? beforeBodyWrite(body, response, false)
                    : beforeBodyWrite(body, request, response);
        } finally {
            RESPONSE_RESULT_WRAPPER_HOLDER.remove();
        }
    }


    /**
     * 处理并返回
     *
     * @param body        响应信息
     * @param response    {@link ServerHttpResponse}
     * @param isBasicType 是否基本数据类型或者包装类型
     * @return 包装后的响应信息
     */
    private Object beforeBodyWrite(Object body, ServerHttpResponse response, boolean isBasicType) {
        Object newBody = isBasicType ? body + ConstantPool.EMPTY : JackSonUtil.getMapper().convertValue(body, JsonNode.class);
        Result result = japiResponseWrapper.wrapper(newBody);
        response.getHeaders().addAll(result.getHead());
        return result.getData();
    }

    /**
     * 处理并返回
     *
     * @param body     响应信息
     * @param request  {@link ServerHttpRequest}
     * @param response {@link ServerHttpResponse}
     * @return 包装后的响应信息
     */
    private Object beforeBodyWrite(Object body, ServerHttpRequest request, ServerHttpResponse response) {
        JapiDecryptHttpServletRequestWrapper japiRequest = cast(request);
        Result result =
                japiResponseWrapper.wrapper(body, japiRequest.getAlgorithm().getEadaMapper(true, japiRequest.getJapiEada()));
        response.getHeaders().addAll(result.getHead());
        return result.getData();
    }

    /**
     * 设置204
     */
    private Empty noContent(ServerHttpResponse response) {
        response.setStatusCode(HttpStatus.NO_CONTENT);
        Result result = japiResponseWrapper.wrapper(ConstantPool.EMPTY);
        response.getHeaders().addAll(result.getHead());
        return Empty.of();
    }

    /**
     * 将{@link ServerHttpRequest}转换{@link JapiDecryptHttpServletRequestWrapper}
     *
     * @param request
     * @return
     */
    private JapiDecryptHttpServletRequestWrapper cast(ServerHttpRequest request) {
        // 获取request
        ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) request;
        return (JapiDecryptHttpServletRequestWrapper) servletServerHttpRequest.getServletRequest();
    }
}
